package com.eastfair.gateway.filters;

import cn.hutool.Hutool;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.eastfair.core.base.R;
import com.eastfair.core.context.ContextConstants;
import com.eastfair.core.context.ContextUtil;
import com.eastfair.core.exception.BizException;
import com.eastfair.core.utils.HumpUtil;
import com.eastfair.handler.EfTokenHandler;
import com.eastfair.jwt.model.AuthInfo;
import com.eastfair.jwt.service.JwtService;
import com.eastfair.properties.IgnoreProperties;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.units.qual.A;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 鉴权过滤器
 *
 * @Author ligang
 * @Date 2020/3/16 23:33
 */
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {
    @Autowired
    JwtService jwtService;

    @Autowired
    EfTokenHandler efTokenHandler;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        ServerHttpRequest.Builder mutate = request.mutate();
        //特殊处理，将前端header头里的信息改变名字放到request里，是前端header和body 参数属性名无法保持一致
        loadHeaderOfLeader(mutate,request);
        //灰度发布参数
        String grayVersion = getHeader(ContextConstants.GRAY_VERSION, request);
        ContextUtil.setGrayVersion(grayVersion);
        // 判断接口是否需要忽略token验证
        if (isSkipPath(request.getPath().toString())) {
            log.debug("当前接口：{}, 不解析用户token", request.getPath().toString());
            return chain.filter(exchange);
        }

        // 获取请求头中的token
        String token = getHeader(ContextConstants.BEARER_HEADER_KEY, request);
        if (token == null || token.isEmpty() || !token.startsWith("Bearer ")) {
            // 封装错误信息
            Map<String, Object> responseData = Maps.newHashMap();
            responseData.put("code", 401);
            responseData.put("message", "非法请求");
            responseData.put("cause", "Token is empty");
            // 输出错误信息到页面
            return responseMsg(responseData,response);
        }

        token = token.replace("Bearer ", "");
        AuthInfo authInfo = null;
        try {
            authInfo = jwtService.parse(token);
//            if (!isTokenClash(token, request, authInfo)) {
//                // 封装错误信息
//                Map<String, Object> responseData = Maps.newHashMap();
//                responseData.put("code", 403);
//                responseData.put("message", "token冲突，请重新登录");
//                responseData.put("cause", "Token is clash");
//                // 输出错误信息到页面
//                return responseMsg(responseData,response);
//            }
        } catch (BizException e) {
            return errorResponse(response, e.getMessage(), e.getCode(), HttpStatus.UNAUTHORIZED);
        } catch (Exception e) {
            e.printStackTrace();
            return errorResponse(response, "验证token出错", R.FAIL_CODE, HttpStatus.UNAUTHORIZED);
        }
        // 将请求头中的token解析出来的用户信息重新封装到请求头，转发到业务服务
        // 业务服务在利用HeaderThreadLocalInterceptor拦截器将请求头中的用户信息解析到ThreadLocal中
        if (authInfo != null) {
            loadHeaderOfAuthInfo(mutate, authInfo);
        }
        return chain.filter(exchange);
    }

    /**
     * 返回错误信息
     * @param responseData
     * @param response
     * @return
     */
    private Mono<Void> responseMsg(Map<String, Object> responseData,ServerHttpResponse response){
        try {
            // 将信息转换为 JSON
            ObjectMapper objectMapper = new ObjectMapper();
            byte[] data = objectMapper.writeValueAsBytes(responseData);
            // 输出错误信息到页面
            DataBuffer buffer = response.bufferFactory().wrap(data);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
            return response.writeWith(Mono.just(buffer));
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * token 端是否冲突
     */
    private boolean isTokenClash(String token, ServerHttpRequest request, AuthInfo authInfo) {
        String terminalType = getHeader(ContextConstants.TERMINAL_TYPE, request);
        //查询以前的token是否冲突
        String lastToken = efTokenHandler.findToken(terminalType, authInfo.getUserId());
        if (StringUtils.isNotBlank(lastToken)) {
            if (!lastToken.equals(token)) {
                return false;
            }
        } else {
            //添加到redis
            efTokenHandler.addToken(token, terminalType, authInfo.getUserId());
        }

        return true;
    }

    /**
     * 将用户信息加载到request header头里
     *
     * @param mutate
     * @param authInfo
     */
    private void loadHeaderOfAuthInfo(ServerHttpRequest.Builder mutate, AuthInfo authInfo) {
        addHeader(mutate, ContextConstants.JWT_KEY_ACCOUNT, authInfo.getAccount());
        addHeader(mutate, ContextConstants.JWT_KEY_USER_ID, authInfo.getUserId());
        addHeader(mutate, ContextConstants.JWT_KEY_NAME, authInfo.getName());
        addHeader(mutate, ContextConstants.JWT_KEY_PROJECT_ID, authInfo.getProjectId());
        addHeader(mutate, ContextConstants.JWT_KEY_SUBJECTTYPE, authInfo.getSubjectType());
        MDC.put(ContextConstants.JWT_KEY_USER_ID, String.valueOf(authInfo.getUserId()));
    }
    /**
     * 特殊处理，将前端header头里的信息改变名字放到request里，是前端header和body 参数属性名无法保持一致
     *
     * @param mutate
     * @param request
     */
    private void loadHeaderOfLeader(ServerHttpRequest.Builder mutate,ServerHttpRequest request) {
        //需要转驼峰的值
        List<String> headerParamList = Arrays.asList(ContextConstants.EXHIBITION_ID,ContextConstants.LANGUAGE_TYPE,
                ContextConstants.TERMINAL_DEVICE,ContextConstants.TERMINAL_TYPE,ContextConstants.SUBJECT_TYPE,ContextConstants.JWT_KEY_PROJECT_ID);
        for(String s: headerParamList){
            String value = getHeader(HumpUtil.humpToLine2(s), request);
            if(StringUtils.isNotBlank(value)){
                addHeader(mutate, s,value);
            }
        }
    }
    private void addHeader(ServerHttpRequest.Builder mutate, String name, Object value) {
        if (value == null) {
            return;
        }
        String valueStr = value.toString();
        String valueEncode = URLUtil.encode(valueStr);
        mutate.header(name, valueEncode);
    }

    protected String getHeader(String headerName, ServerHttpRequest request) {
        HttpHeaders headers = request.getHeaders();
        String token = StrUtil.EMPTY;
        if (headers == null || headers.isEmpty()) {
            return token;
        }

        token = headers.getFirst(headerName);

        if (StrUtil.isNotBlank(token)) {
            return token;
        }

        return request.getQueryParams().getFirst(headerName);
    }


    /**
     * 设置过滤器的执行顺序
     *
     * @return
     */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
//        return Ordered.LOWEST_PRECEDENCE;
    }

    protected Mono<Void> errorResponse(ServerHttpResponse response, String errMsg, int errCode, HttpStatus httpStatus) {
        R tokenError = R.fail(errCode, errMsg);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        response.setStatusCode(httpStatus);
        DataBuffer dataBuffer = response.bufferFactory().wrap(tokenError.toString().getBytes());
        return response.writeWith(Mono.just(dataBuffer));
    }


    /**
     * @desc 校验url是否跳过
     * @author ZZZ
     * @date 2019/2/20 0020
     * @version 1.0
     */
    private boolean isSkipPath(String uri) {
        return IgnoreProperties.isIgnoreToken(uri);
    }
}
